package edu.uci.ics.jung.visualization.renderers;

import java.awt.Dimension;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JComponent;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.transform.LensTransformer;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;

public class BasicEdgeRenderer<V, E>
  implements Renderer.Edge<V, E>
{
  protected EdgeArrowRenderingSupport edgeArrowRenderingSupport;

  public BasicEdgeRenderer()
  {
    this.edgeArrowRenderingSupport = new BasicEdgeArrowRenderingSupport();
  }

  public void paintEdge(RenderContext<V, E> rc, Layout<V, E> layout, E e) {
    GraphicsDecorator g2d = rc.getGraphicsContext();
    Graph graph = layout.getGraph();

   
    Pair<V> endpoints = graph.getEndpoints(e);
    if (endpoints == null) {
    	return;
    }
    Object v1 = endpoints.getFirst();
    Object v2 = endpoints.getSecond();

    Stroke new_stroke = (Stroke)rc.getEdgeStrokeTransformer().transform(e);
    Stroke old_stroke = g2d.getStroke();
    if (new_stroke != null) {
      g2d.setStroke(new_stroke);
    }
    drawSimpleEdge(rc, layout, e);

    if (new_stroke != null)
      g2d.setStroke(old_stroke);
  }

  protected void drawSimpleEdge(RenderContext<V, E> rc, Layout<V, E> layout, E e)
  {
    GraphicsDecorator g = rc.getGraphicsContext();
    Graph<V, E> graph = layout.getGraph();
    Pair endpoints = graph.getEndpoints(e);
    if (endpoints == null) {
    	return;
    }
    Object v1 = endpoints.getFirst();
    Object v2 = endpoints.getSecond();

    Point2D p1 = (Point2D)layout.transform((V) v1);
    Point2D p2 = (Point2D)layout.transform((V) v2);
    p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
    p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
    float x1 = (float)p1.getX();
    float y1 = (float)p1.getY();
    float x2 = (float)p2.getX();
    float y2 = (float)p2.getY();

    boolean isLoop = v1.equals(v2);
    Shape s2 = (Shape)rc.getVertexShapeTransformer().transform((V) v2);
    Shape edgeShape = (Shape)rc.getEdgeShapeTransformer().transform(Context.getInstance(graph, e));

    boolean edgeHit = true;
    boolean arrowHit = true;
    Rectangle deviceRectangle = null;
    JComponent vv = rc.getScreenDevice();
    if (vv != null) {
      Dimension d = vv.getSize();
      deviceRectangle = new Rectangle(0, 0, d.width, d.height);
    }

    AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);

    if (isLoop)
    {
      Rectangle2D s2Bounds = s2.getBounds2D();
      xform.scale(s2Bounds.getWidth(), s2Bounds.getHeight());
      xform.translate(0.0D, -edgeShape.getBounds2D().getWidth() / 2.0D); } else {
      float dx;
      float dy;
      if (rc.getEdgeShapeTransformer() instanceof EdgeShape.Orthogonal) {
        dx = x2 - x1;
        dy = y2 - y1;
        int index = 0;
        if (rc.getEdgeShapeTransformer() instanceof EdgeShape.IndexedRendering) {
          EdgeIndexFunction peif = ((EdgeShape.IndexedRendering)rc.getEdgeShapeTransformer()).getEdgeIndexFunction();

          index = peif.getIndex(graph, e);
          index *= 20;
        }
        GeneralPath gp = new GeneralPath();
        gp.moveTo(0.0F, 0.0F);
        if (x1 > x2) {
          if (y1 > y2) {
            gp.lineTo(0.0F, index);
            gp.lineTo(dx - index, index);
            gp.lineTo(dx - index, dy);
            gp.lineTo(dx, dy);
          } else {
            gp.lineTo(0.0F, -index);
            gp.lineTo(dx - index, -index);
            gp.lineTo(dx - index, dy);
            gp.lineTo(dx, dy);
          }

        }
        else if (y1 > y2) {
          gp.lineTo(0.0F, index);
          gp.lineTo(dx + index, index);
          gp.lineTo(dx + index, dy);
          gp.lineTo(dx, dy);
        }
        else {
          gp.lineTo(0.0F, -index);
          gp.lineTo(dx + index, -index);
          gp.lineTo(dx + index, dy);
          gp.lineTo(dx, dy);
        }

        edgeShape = gp;
      }
      else
      {
        dx = x2 - x1;
        dy = y2 - y1;
        float thetaRadians = (float)Math.atan2(dy, dx);
        xform.rotate(thetaRadians);
        float dist = (float)Math.sqrt(dx * dx + dy * dy);
        xform.scale(dist, 1.0D);
      }
    }
    edgeShape = xform.createTransformedShape(edgeShape);

    MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
    if (vt instanceof LensTransformer) {
      vt = ((LensTransformer)vt).getDelegate();
    }
    edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);

    if (edgeHit != true)
      return;
    Paint oldPaint = g.getPaint();

    Paint fill_paint = (Paint)rc.getEdgeFillPaintTransformer().transform(e);
    if (fill_paint != null)
    {
      g.setPaint(fill_paint);
      g.fill(edgeShape);
    }
    Paint draw_paint = (Paint)rc.getEdgeDrawPaintTransformer().transform(e);
    if (draw_paint != null)
    {
      g.setPaint(draw_paint);
      g.draw(edgeShape);
    }

    float scalex = (float)g.getTransform().getScaleX();
    float scaley = (float)g.getTransform().getScaleY();

    if ((scalex < 0.3D) || (scaley < 0.3D)) return;

    if (rc.getEdgeArrowPredicate().evaluate(Context.getInstance(graph, e)))
    {
      Stroke new_stroke = (Stroke)rc.getEdgeArrowStrokeTransformer().transform(e);
      Stroke old_stroke = g.getStroke();
      if (new_stroke != null) {
        g.setStroke(new_stroke);
      }

      Shape destVertexShape = (Shape)rc.getVertexShapeTransformer().transform(graph.getEndpoints(e).getSecond());

      AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2);
      destVertexShape = xf.createTransformedShape(destVertexShape);

      arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle);
      if (arrowHit)
      {
        AffineTransform at = this.edgeArrowRenderingSupport.getArrowTransform(rc, edgeShape, destVertexShape);

        if (at == null) return;
        Shape arrow = (Shape)rc.getEdgeArrowTransformer().transform(Context.getInstance(graph, e));
        arrow = at.createTransformedShape(arrow);
        g.setPaint((Paint)rc.getArrowFillPaintTransformer().transform(e));
        g.fill(arrow);
        g.setPaint((Paint)rc.getArrowDrawPaintTransformer().transform(e));
        g.draw(arrow);
      }
      if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
        Shape vertexShape = (Shape)rc.getVertexShapeTransformer().transform(graph.getEndpoints(e).getFirst());

        xf = AffineTransform.getTranslateInstance(x1, y1);
        vertexShape = xf.createTransformedShape(vertexShape);

        arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle);

        if (arrowHit) {
          AffineTransform at = this.edgeArrowRenderingSupport.getReverseArrowTransform(rc, edgeShape, vertexShape, !(isLoop));
          if (at == null) return;
          Shape arrow = (Shape)rc.getEdgeArrowTransformer().transform(Context.getInstance(graph, e));
          arrow = at.createTransformedShape(arrow);
          g.setPaint((Paint)rc.getArrowFillPaintTransformer().transform(e));
          g.fill(arrow);
          g.setPaint((Paint)rc.getArrowDrawPaintTransformer().transform(e));
          g.draw(arrow);
        }
      }

      if (new_stroke != null) {
        g.setStroke(old_stroke);
      }

    }

    g.setPaint(oldPaint);
  }

  public EdgeArrowRenderingSupport getEdgeArrowRenderingSupport()
  {
    return this.edgeArrowRenderingSupport;
  }

  public void setEdgeArrowRenderingSupport(EdgeArrowRenderingSupport edgeArrowRenderingSupport)
  {
    this.edgeArrowRenderingSupport = edgeArrowRenderingSupport;
  }
}